/**
 * <p>Original Author: Daniel Freeman    http://www.danielfreeman.asia</p>
 *
 * <p>Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:</p>
 *
 * <p>The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.</p>
 *
 * <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS' OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.</p>
 *
 * <p>Licensed under The MIT License</p>
 * <p>Redistributions of files must retain the above copyright notice.</p>
 * <p>You are permitted to modify and enhance this code,</p>
 * <p>As a courtesy, please credit the original author, Daniel Freeman   http://www.danielfreeman.asia</p>
 */


// Procedural mesh generation of corridor, with floor reflection.  As if the floor were a mirror.

package
{
	[SWF(frameRate="60")]	
	
	import com.adobe.utils.*;
	
	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DBlendFactor;
	import flash.display3D.Context3DCompareMode;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.display3D.textures.Texture;
	import flash.events.Event;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	
	
	public class CorridorExperiment1 extends Sprite {
		
		protected static const CORRIDOR_COORDS:Vector.<Number> = Vector.<Number>([0,0, 0,0.05, -0.1,0.1, -0.15,0.75, 0.5,0.85]);
		protected static const CORRIDOR_SEGMENT_WIDTH:Number = 0.5;
		protected static const PARTITION_COORDS:Vector.<Number> = Vector.<Number>([0.03,0, -0.05,0.7, 0.5,0.85]);
		protected static const PARTITION_WIDTH:Number = 0.05;
		protected static const PARTITION_DEPTH:Number = 0.2;
		
		protected static const STEPS:int = 14;
		protected static const STEPSM:int = STEPS - 1;
		
		protected static const CORRIDOR_ANGLE:Number = 20.0;
		
		protected static const N:int = 5;
		
		protected var _indices:Vector.<uint> = new Vector.<uint>;
		protected var _reverseIndices:Vector.<uint> = new Vector.<uint>;
		
		protected var _vertices:Vector.<Number> = new Vector.<Number>;
		
		protected var _context3D:Context3D;
		protected var _shaderProgram:Program3D;
		protected var _vertexBuffer:VertexBuffer3D;
		
		protected var _indexBuffer:IndexBuffer3D;  // Indices of the corridor
		protected var _reverseIndexBuffer:IndexBuffer3D; //Indices of the reflection
		
		// Transformation matrices
		protected var _modelMatrix:Matrix3D = new Matrix3D();
		protected var _rotationMatrix:Matrix3D;
		protected var _finalMatrix:Matrix3D = new Matrix3D();
		
		protected var _projectionMatrix:PerspectiveMatrix3D = new PerspectiveMatrix3D();
		
		protected var _t:Number = 0;
		
		public function CorridorExperiment1(screen:Sprite = null) {
			if (screen)
				screen.addChild(this);
			
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			
			generateCorridor(15);
			
			trace("vertices="+_vertices);
			trace("vertices.length="+_vertices.length);
			trace("indices="+_indices);
			
			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
		}
		
		
		protected function onContext3DCreate(event:Event):void {				
			_context3D = Stage3D(event.currentTarget).context3D;

			if (!_context3D)
				return;

			_context3D.enableErrorChecking = true;
			initialiseData();
		}
		
		// Procedural mesh generation of corridor
		protected function 	generateCeiling(position:Number, length:Number, width:Number, height:Number, n:int, anglePerUnit:Number = 0, gap:Number = 0, winding:Boolean = true):void {
			const rotate:Number = CORRIDOR_ANGLE*width;
			var start:int = _vertices.length/N;
			var transform:Matrix3D = new Matrix3D();
			transform.appendRotation(rotate, Vector3D.Y_AXIS);
			for (var i:int = 0; i < n; i++) {
				var vector0:Vector3D = transform.transformVector(new Vector3D(position, height, 0.0));
				var vector1:Vector3D = transform.transformVector(new Vector3D(position + length, height, 0.0));
				_vertices.push(vector0.x, vector0.y, vector0.z, 0.0, (i%2==0) ? 0.0 : 1.0);
				_vertices.push(vector1.x, vector1.y, vector1.z, 1.0, (i%2==0) ? 0.0 : 1.0);
				transform.appendTranslation(0.0, 0.0, width);
				transform.appendRotation(rotate, Vector3D.Y_AXIS);
			}
			var indices:Vector.<uint> = winding ? _indices : _reverseIndices;
			var reverseIndices:Vector.<uint> = winding ? _reverseIndices : _indices;
			for (var j:int = start; j < start + n - 2; j+=2) {
				indices.push(j+3, j+1, j, j+2, j+3, j);
				reverseIndices.push(j, j+1, j+3, j, j+3, j+2);
			}
		}
		
		// Procedural mesh generation of corridor
		protected function generateCorridor(n:int):void {
			const rotate:Number = CORRIDOR_ANGLE*(CORRIDOR_SEGMENT_WIDTH + PARTITION_WIDTH);
			const extra:Number = 1.0*Math.tan(-rotate*Math.PI/180);
			var transformLeft:Matrix3D = new Matrix3D();
			var transformRight:Matrix3D = new Matrix3D();
			transformRight.appendScale(-1.0, 1.0, 1.0);
		//	transformLeft.appendTranslation(0.0, 0.0, -PARTITION_WIDTH);
			transformRight.appendTranslation(1.0, 0.0, 0.0);//-extra/2); //-PARTITION_WIDTH-extra/2);
			transformLeft.appendRotation(rotate, Vector3D.Y_AXIS);
			transformRight.appendRotation(rotate, Vector3D.Y_AXIS);
			generatePartition(PARTITION_COORDS, -PARTITION_WIDTH ,PARTITION_WIDTH, PARTITION_DEPTH, transformLeft);
			generatePartition(PARTITION_COORDS, -PARTITION_WIDTH, PARTITION_WIDTH, PARTITION_DEPTH, transformRight);
			for (var i:int = 0; i < n; i++) {
				generateCorridorSegment(CORRIDOR_COORDS, CORRIDOR_SEGMENT_WIDTH, transformLeft);
				generateCorridorSegment(CORRIDOR_COORDS, CORRIDOR_SEGMENT_WIDTH+extra, transformRight, false);

				generatePartition(PARTITION_COORDS, CORRIDOR_SEGMENT_WIDTH, PARTITION_WIDTH, PARTITION_DEPTH, transformLeft);
				generatePartition(PARTITION_COORDS, CORRIDOR_SEGMENT_WIDTH+extra, PARTITION_WIDTH, PARTITION_DEPTH, transformRight);
				
				transformLeft.appendTranslation(0, 0, CORRIDOR_SEGMENT_WIDTH + PARTITION_WIDTH);
				transformRight.appendTranslation(0, 0, CORRIDOR_SEGMENT_WIDTH + PARTITION_WIDTH);
				transformLeft.appendRotation(rotate, Vector3D.Y_AXIS);
				transformRight.appendRotation(rotate, Vector3D.Y_AXIS);
			}
			generateCeiling(0.5, 0.7, 0.4, 0.7, 4*((CORRIDOR_SEGMENT_WIDTH+ PARTITION_WIDTH+extra)*n)/0.2);
		}
		
		
		protected function generateCorridorSegment(coordinates:Vector.<Number>, width:Number, transform:Matrix3D, winding:Boolean = true):void {
			var start:int = _vertices.length/N;
			for (var i:int = 0; i < coordinates.length; i+=2) {
				var x0:Number = coordinates[i];
				var y0:Number = coordinates[i+1];
				var vector0:Vector3D = transform.transformVector(new Vector3D(x0, y0, 0.0));
				var vector:Vector3D = transform.transformVector(new Vector3D(x0, y0, width));
				_vertices.push(vector0.x, vector0.y, vector0.z, 0.0, (i%4==0) ? 0.0 : 1.0);
				_vertices.push(vector.x, vector.y, vector.z, 1.0, (i%4==0) ? 0.0 : 1.0);
			}
			var indices:Vector.<uint> = winding ? _indices : _reverseIndices;
			var reverseIndices:Vector.<uint> = winding ? _reverseIndices : _indices;
			for (var j:int = start; j < start + coordinates.length - 2; j+=2) {
				indices.push(j, j+1, j+3, j, j+3, j+2);
				reverseIndices.push(j+3, j+1, j, j+2, j+3, j);
			}
		}
		
		
		protected function generatePartition(coordinates:Vector.<Number>, z:Number, width:Number, depth:Number, transform:Matrix3D, winding:Boolean = true):void {
			var start:int = _vertices.length/N;
			for (var i:int = 0; i < coordinates.length; i+=2) {
				var x0:Number = coordinates[i];
				var y0:Number = coordinates[i+1];
				var vector0:Vector3D = transform.transformVector(new Vector3D(-depth, y0, z));
				var vector1:Vector3D = transform.transformVector(new Vector3D(x0, y0, z));
				var vector2:Vector3D = transform.transformVector(new Vector3D(x0, y0, z+width));
				var vector3:Vector3D = transform.transformVector(new Vector3D(-depth, y0, z+width));
				_vertices.push(vector0.x, vector0.y, vector0.z, 0.0, (i%4==0) ? 0.0 : 1.0);
				_vertices.push(vector1.x, vector1.y, vector1.z, 1.0, (i%4==0) ? 0.0 : 1.0);
				_vertices.push(vector2.x, vector2.y, vector2.z, 0.0, (i%4==0) ? 0.0 : 1.0);
				_vertices.push(vector3.x, vector3.y, vector3.z, 1.0, (i%4==0) ? 0.0 : 1.0);
			}
			var indices:Vector.<uint> = winding ? _indices : _reverseIndices;
			var reverseIndices:Vector.<uint> = winding ? _reverseIndices : _indices;
			for (var j:int = start; j < start + coordinates.length; j+=4) {
				indices.push(j, j+1, j+5, j, j+5, j+4);
				indices.push(j+1, j+2, j+6, j+1, j+6, j+5);
				indices.push(j+6, j+2, j+3, j+7, j+6, j+3);
				
				reverseIndices.push(j+5, j+1, j, j+4, j+5, j);
				reverseIndices.push(j+6, j+2, j+1, j+5, j+6, j+1);
				reverseIndices.push(j+3, j+2, j+6, j+3, j+6, j+7);
			}
		}
		
		
		protected function generateSphereVerticesAndCodes():void {
			_vertices.push(0.0, -1.0, 0.0, 0.0, 0.0);
			for (var i:int = 0; i < STEPS; i++) {
				for (var j:int = 1; j < STEPS; j++) {
					var phi:Number = 2 * Math.PI * (i / STEPS);
					var theta:Number = Math.PI * (j / STEPS - 0.5);
					_vertices.push(
						Math.cos(theta)*Math.cos(phi),
						Math.sin(theta),
						Math.cos(theta)*Math.sin(phi),
						(i%2==0) ? 0.0 : 1.0,
						(j%2==0) ? 0.0 : 1.0
					);
				}
			}
			_vertices.push(0.0, 1.0, 0.0, 0.0, 0.0);
		}
		
		
		protected function generateSphereIndices():void {
			var lastLine:int = STEPSM*STEPSM;
			for (var i:int = 0; i < STEPS; i++) {
				_indices.push(0, i*STEPSM + 1, lastLine+1);
				for (var j:int = 1; j < STEPSM; j++) {
					_indices.push(lastLine+j, i*STEPSM+j+1, lastLine+j+1);
					_indices.push(lastLine+j, i*STEPSM+j, i*STEPSM+j+1);
				}
				_indices.push(lastLine+STEPSM, (i+1)*STEPSM, _vertices.length / N - 1);
				lastLine = i * STEPSM;
			}
		}
		
		
		protected function initialiseData():void {			
			_context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight,0, true);
			_context3D.setCulling(Context3DTriangleFace.FRONT);
			
			// Upload the indexes
			_indexBuffer = _context3D.createIndexBuffer( _indices.length );
			_indexBuffer.uploadFromVector(_indices, 0, _indices.length );
			
			_reverseIndexBuffer = _context3D.createIndexBuffer( _reverseIndices.length );
			_reverseIndexBuffer.uploadFromVector(_reverseIndices, 0, _reverseIndices.length );

			
			// Upload the vertex data
			_vertexBuffer = _context3D.createVertexBuffer( _vertices.length/N, N); 
			_vertexBuffer.uploadFromVector(_vertices, 0, _vertices.length/N);
			
			// A simple vertex shader which does a 3D transformation
			var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
				"mov v0, va1 \n" +						// 
				"m44 op, va0, vc0 \n"					// translate vertex
			);
			
			// Wireframe fragment shader
			var fragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
				"sub ft0.x, fc0.w, v0.x \n" +
				"sub ft0.y, fc0.w, v0.y \n" +
				
				"min ft0.z, ft0.x, ft0.y \n" +
				"min ft0.z, ft0.z, v0.x \n" +
				"min ft0.z, ft0.z, v0.y \n" +
				
				"div ft0.z, fc1.w, ft0.z \n" +
				"sat ft0.z, ft0.z \n" +
				
				"mul ft1.xyz, fc0.xyz, ft0.zzz \n" +
				"add oc, fc1.xyz, ft1.xyz"
			);
			
			// Combine assembled shaders into a program
			_shaderProgram = _context3D.createProgram();
			_shaderProgram.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
			
			// Perspective transformation
			_projectionMatrix.perspectiveFieldOfViewLH(45.0*Math.PI/180,stage.stageWidth/stage.stageHeight,0.1,1000.0);
			
			_modelMatrix.identity();
			_modelMatrix.appendTranslation(-0.5, -0.4, 0.8);
			_rotationMatrix = _modelMatrix.clone();
			
			// Start animating
			addEventListener(Event.ENTER_FRAME,enterFrame);
		}
		
		
		protected function normalise4(p:Number, q:Number, r:Number, quotient:Number = 0):Vector.<Number> {
			if (quotient==0)
				quotient = Math.sqrt(p * p + q * q + r * r);
			return Vector.<Number>([ p/quotient, q/quotient, r/quotient, 1.0 ]);
		}
		
		
		protected function enterFrame(event:Event):void {
			const rotate:Number = -CORRIDOR_ANGLE*0.04;
			_context3D.clear();
			
			_context3D.setVertexBufferAt( 0, _vertexBuffer,  0, Context3DVertexBufferFormat.FLOAT_3 );  //va0
			_context3D.setVertexBufferAt( 1, _vertexBuffer,  3, Context3DVertexBufferFormat.FLOAT_2 );  //va1
			
			_context3D.setProgram ( _shaderProgram );
			
			if (_t<100) {
				_modelMatrix.appendTranslation(0, 0, -0.04);
				_modelMatrix.appendRotation(rotate, Vector3D.Y_AXIS);
			}
			else {
				_t = 0;
				_modelMatrix = _rotationMatrix.clone();
			}
			
			_modelMatrix.copyToMatrix3D(_finalMatrix);
			_finalMatrix.append(_projectionMatrix);
			
			_t+=1;
			
			_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _finalMatrix, true);		// va0 - va3
			//	_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 4, _rotationMatrix, true);	// va4 - va7
			
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([ 0.4, 0.4, 0.4, 1.0 ]) );	// fc4 Specular Colour
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([ 0.0, 0.0, 0.2, 0.04 ]) ); // fc3 Diffuse Colour
			_context3D.drawTriangles( _indexBuffer);

			_modelMatrix.copyToMatrix3D(_finalMatrix);
			_finalMatrix.appendScale(1.0, -1.0, 1.0);
			_finalMatrix.appendTranslation(0.0, -0.8, 0.0);
			_finalMatrix.append(_projectionMatrix);
			_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _finalMatrix, true);
			_context3D.drawTriangles( _reverseIndexBuffer);
			
			_context3D.present();
		}
		
	}
}